/*
 * @Author: xiaosihan
 * @Date: 2023-09-01 00:09:29
 * @Last Modified by: xiaosihanosihan
 * @Last Modified time: 2025-08-29 20:24:27
 */
import { cache, classRegistry, IText, Path } from 'fabric';
import get from "lodash/get";
import brContorls from './brContorls';
import fabricUtils from './fabricUtils';
import fontManage from './fontManage';
import tlContorls from './tlContorls';
import trContorls from "./trContorls";

const JUSTIFY = 'justify';
const JUSTIFY_LEFT = 'justify-left';
const JUSTIFY_RIGHT = 'justify-right';
const JUSTIFY_CENTER = 'justify-center';

const CENTER = 'center';
const LEFT = 'left';
const TOP = 'top';
const BOTTOM = 'bottom';
const RIGHT = 'right';
const NONE = 'none';

// 文字组件
export default class FabricText extends IText {
    constructor(text = 'FabricText', options: any = {}) {
        super(text, options);
        this.id = get(options, ['id']) || Date.now();
        this.name = options.name || '';
        this.editable = !['name', 'num'].includes(this.name);
        this.stroke = options.stroke || "#000000";
        this.strokeWidth = options.strokeWidth || 0;
        this.originWidth = this.width;
        this.originHeight = this.height;
        this.bendDegree = options.bendDegree || 0;

        this.originX = "center";
        this.originY = "center";
        this.cornerStyle = 'circle';

        // 默认字体
        this.fontFamily = options.fontFamily || (fontManage.fontlist[0] ? fontManage.fontlist[0].fontName : "");
        // 设置控制器都不可见
        this.setControlsVisibility({
            bl: false,
            br: true,
            tl: true,
            tr: true,
            mtr: false,
            mb: false,
            ml: false,
            mr: false,
            mt: false,
        });
        this.controls.br = brContorls;
        this.controls.tl = tlContorls;
        this.controls.tr = trContorls;
        this.initDimensions();
        this._updatePath();
    }

    borderColor = "#fd7901";
    id: number = 0;
    transparentCorners = false;
    cornerStrokeColor = '#cccccc';
    cornerColor = '#ffffff';
    cornerSize = 30;
    touchCornerSize = 120;
    borderScaleFactor = 2;
    // 原始宽高 在名字和号码的预览功能会用到，保持原有文字的宽度
    fontSize = 50;
    lineHeight = 1;
    originWidth = 400;
    originHeight = 100;
    stroke = '#000000';
    strokeWidth = 0;
    bendDegree = 0; // 文字弯曲的角度
    name = '';
    //关闭缓存
    objectCaching = false;
    // 可以不导出的默认值
    static defaultOption = {
        angle: 0,
        backgroundColor: '',
        charSpacing: 0,
        direction: 'ltr',
        fill: 'rgb(0,0,0)',
        fillRule: 'nonzero',
        flipX: false,
        flipY: false,
        fontStyle: 'normal',
        fontWeight: 'normal',
        globalCompositeOperation: 'source-over',
        linethrough: false,
        opacity: 1,
        originX: 'center',
        originY: 'center',
        overline: false,
        paintFirst: 'fill',
        path: null,
        pathAlign: 'baseline',
        pathSide: 'left',
        pathStartOffset: 0,
        scaleX: 1,
        scaleY: 1,
        shadow: null,
        skewX: 0,
        skewY: 0,
        stroke: null,
        strokeDashArray: null,
        strokeDashOffset: 0,
        strokeLineCap: 'butt',
        strokeLineJoin: 'miter',
        strokeMiterLimit: 4,
        strokeUniform: false,
        strokeWidth: null,
        styles: [],
        textAlign: 'left',
        textBackgroundColor: '',
        underline: false,
        visible: true,
    };

    // objectCaching = false;
    noScaleCache = false;
    // 缩小时不翻转
    lockScalingFlip = true;
    // 同时处理文字元素 最小宽高, 取消全选不跑偏问题
    setPositionByOrigin(pos, originX, originY) {
        var center = super.translateToCenterPoint(pos, originX, originY),
            position = super.translateToOriginPoint(center, this.originX, this.originY);
        if (this.canvas && this.canvas.getActiveObject() !== this) {
            super.set('left', position.x);
            super.set('top', position.y);
        }
    }
    set(...options) {
        if (options[0] === "type") {
            return this;
        }
        //@ts-ignore
        const object = super.set(...options);
        if (['name', 'num'].includes(this.name) && ['scaleX', 'scaleY'].includes(options[0])) {
            this.originWidth = this.width;
            this.originHeight = this.height;
        }
        if (['text', 'fontFamily', 'fontSize', 'charSpacing', 'lineHeight', 'bendDegree'].includes(options[0])) {
            this._updatePath();
        }
        return object;
    }
    // 计算元素的宽高
    initDimensions() {
        if (this.canvas) {
            super.initDimensions();
        }
        if (this.path) {
            this.width += this.fontSize * 1.5 * Math.sin(fabricUtils.degreesToRadians(Math.min(180, this
                .bendDegree) / 2));
            if (this.bendDegree > 0) {
                this.height += this.fontSize * 1.5;
            }
        }
    }

    // 绘制弯曲的文字
    _updatePath() {
        // 定义参数
        const angle = this.bendDegree; // 弧的夹角（单位：度）
        let chordLength = this._textLines ? this.measureLine(0).width : 0; // 文字总长度

        if (angle <= 5) {
            this.set('path', null);
        } else if (chordLength > 0 && angle > 5) {
            // 计算半径
            const radius = chordLength / (2 * Math.PI * (angle / 360));
            // 计算弧的起始点和结束点
            const startX = Math.sin(fabricUtils.degreesToRadians(angle / 2)) * -radius;
            const startY = 0;
            const endX = Math.sin(fabricUtils.degreesToRadians(angle / 2)) * radius;
            const endY = 0;
            // 创建路径
            const path = new Path(
                `M ${startX},${startY} A ${radius},${radius},0,${angle < 180 ? '0' : '1'},1,${endX},${endY}`, {
                stroke: '#00000000',
                fill: '#00000000',
                strokeWidth: 2,
            });
            this.set({
                path
            });
        }
    }

    //@ts-ignore
    toObject(propertiesToInclude: any[] = []) {
        const object = super.toObject([
            ...propertiesToInclude,
            'name',
            'originWidth',
            'originHeight',
            'bendDegree',
        ]);
        Object.assign(object, {
            styles: {}
        });
        delete object.path;
        return object;
    }

    // toJSON(propertiesToInclude = []) {
    //     const json = super.toJSON([
    //         ...propertiesToInclude,
    //         'name',
    //         'originWidth',
    //         'originHeight',
    //         'bendDegree',
    //     ]);
    //     Object.assign(json, {
    //         styles: {}
    //     });
    //     return json;
    // }

    /**
     * 测量并返回单个字符的宽度。
     * 可能会被重写以适应不同的测量逻辑或
     * 钩住一些外部库进行字符测量
     * @param {String} _char 要测量的字符
     * @param {Object} charStyle 要测量的字符样式
     * @param {String} [previousChar] 前一个字符
     * @param {Object} [prevCharStyle] 前一个字符的样式
     */
    _measureChar(
        _char,
        charStyle,
        previousChar,
        prevCharStyle,
    ) {
        const fontCache = cache.getFontCache(charStyle),
            fontDeclaration = this._getFontDeclaration(charStyle),
            couple = previousChar + _char,
            stylesAreEqual =
                previousChar &&
                fontDeclaration === this._getFontDeclaration(prevCharStyle),
            fontMultiplier = charStyle.fontSize / this.CACHE_FONT_SIZE;
        let width,
            coupleWidth,
            previousWidth,
            kernedWidth;

        if (previousChar && fontCache[previousChar] !== undefined) {
            previousWidth = fontCache[previousChar];
        }
        if (fontCache[_char] !== undefined) {
            kernedWidth = width = fontCache[_char];
        }
        if (stylesAreEqual && fontCache[couple] !== undefined) {
            coupleWidth = fontCache[couple];
            kernedWidth = coupleWidth - previousWidth;
        }
        if (
            width === undefined ||
            previousWidth === undefined ||
            coupleWidth === undefined
        ) {
            const ctx = this.canvas!.getContext();
            // 发送一个 TRUE 以指定测量字体大小 CACHE_FONT_SIZE
            this._setTextStyles(ctx, charStyle, true);
            if (width === undefined) {
                kernedWidth = width = ctx.measureText(_char).width;
                fontCache[_char] = width;
            }
            if (previousWidth === undefined && stylesAreEqual && previousChar) {
                previousWidth = ctx.measureText(previousChar).width;
                fontCache[previousChar] = previousWidth;
            }
            if (stylesAreEqual && coupleWidth === undefined) {
                // 我们可以测量字距调整对并减去前一个字符的宽度
                coupleWidth = ctx.measureText(couple).width;
                fontCache[couple] = coupleWidth;
                // 安全使用非空值，因为如果未定义，我们之前已定义它。
                kernedWidth = coupleWidth - previousWidth;
            }
        }
        return {
            width: width * fontMultiplier,
            kernedWidth: kernedWidth * fontMultiplier,
        };
    }

    /**
     * @param {String} method fillText or strokeText.
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Array} line Content of the line, splitted in an array by grapheme
     * @param {Number} left
     * @param {Number} top
     * @param {Number} lineIndex
     */
    _renderChars(method, ctx, line, left, top, lineIndex) {
        const lineHeight = this.getHeightOfLine(lineIndex),
            isJustify = this.textAlign.includes(JUSTIFY),
            path = this.path,
            shortCut = !isJustify &&
                this.charSpacing === 0 &&
                this.isEmptyStyles(lineIndex) &&
                !path,
            isLtr = this.direction === 'ltr',
            sign = this.direction === 'ltr' ? 1 : -1,
            // this was changed in the PR #7674
            // currentDirection = ctx.canvas.getAttribute('dir');
            currentDirection = ctx.direction;

        let actualStyle,
            nextStyle,
            charsToRender = '',
            charBox,
            boxWidth = 0,
            timeToRender,
            drawingLeft;

        ctx.save();
        if (currentDirection !== this.direction) {
            // ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
            ctx.direction = isLtr ? 'ltr' : 'rtl';
            ctx.textAlign = isLtr ? LEFT : RIGHT;
        }
        top -= (lineHeight * this._fontSizeFraction) / this.lineHeight;
        if (shortCut) {
            // render all the line in one pass without checking
            // drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex);
            this._renderChar(method, ctx, lineIndex, 0, line.join(''), left, top);
            ctx.restore();
            return;
        }
        for (let i = 0, len = line.length - 1; i <= len; i++) {
            timeToRender = i === len || this.charSpacing || path;
            charsToRender += line[i];
            charBox = this.__charBounds[lineIndex][i];
            if (boxWidth === 0) {
                left += sign * (charBox.kernedWidth - charBox.width);
                boxWidth += charBox.width;
            } else {
                boxWidth += charBox.kernedWidth;
            }
            if (isJustify && !timeToRender) {
                if (this._reSpaceAndTab.test(line[i])) {
                    timeToRender = true;
                }
            }
            // if we have charSpacing, we render char by char
            // if (!timeToRender) {
            //     actualStyle =
            //         actualStyle || this.getCompleteStyleDeclaration(lineIndex, i);
            //     nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1);
            //     timeToRender = hasStyleChanged(actualStyle, nextStyle, false);
            // }
            if (timeToRender) {
                if (path) {
                    ctx.save();
                    ctx.translate(charBox.renderLeft, charBox.renderTop);
                    ctx.rotate(charBox.angle);
                    this._renderChar(
                        method,
                        ctx,
                        lineIndex,
                        i,
                        charsToRender,
                        -boxWidth / 2,
                        0,
                    );
                    ctx.restore();
                } else {
                    drawingLeft = left;
                    this._renderChar(
                        method,
                        ctx,
                        lineIndex,
                        i,
                        charsToRender,
                        drawingLeft,
                        top,
                    );
                }
                charsToRender = '';
                actualStyle = nextStyle;
                left += sign * boxWidth;
                boxWidth = 0;
            }
        }
        ctx.restore();
    }

}

// 覆盖全局注册
classRegistry.setClass(FabricText, 'Text');
classRegistry.setClass(FabricText, 'i-text');